import ctypes
import time
import cv2
import numpy as np
import pyautogui
from typing import Optional


class Target:
    """目标点控制类，用于执行鼠标移动、点击和屏幕模板匹配等操作。
    
    该类提供了一系列方法来控制鼠标在屏幕上的移动和点击，
    以及通过模板匹配在屏幕上查找特定图像。
    
    示例:
        # 创建一个目标点并执行操作
        >>> target = Target(100, 200)
        >>> target.Move().Wait(1).Click()  # 链式调用
        
        # 查找屏幕上的模板图像
        >>> template_path = "模板图像.png"  # 可以包含中文路径
        >>> found_target = Target.FindTemplateImageOnScreen(template_path)
        >>> if found_target:
        ...     found_target.Click()  # 如果找到则点击
        
        # 检查模板是否存在
        >>> if Target.IsTemplateExistOnDesktop(template_path):
        ...     print("找到模板")
        
        # 检查点是否在特定窗口内
        >>> if target.IsExistOnWindow("记事本"):
        ...     print("点在记事本窗口内")
    """
    def __init__(self, x: int, y: int) -> None:
        """初始化目标点对象。
        
        Args:
            x: 目标点的X坐标
            y: 目标点的Y坐标
        """
        self.x = x
        self.y = y
    
    def Move(self) -> 'Target':
        """移动鼠标到目标点位置。
        
        Returns:
            Target: 返回自身实例，支持链式调用
        """
        Target.锁定键盘鼠标()
        pyautogui.moveTo(self.x, self.y)
        Target.解锁键盘鼠标()
        return self
    
    def Click(self) -> 'Target':
        """移动鼠标到目标点位置并点击左键。
        
        Returns:
            Target: 返回自身实例，支持链式调用
        """
        Target.锁定键盘鼠标()
        self.Move()
        pyautogui.leftClick()
        Target.解锁键盘鼠标()
        return self
    
    def Wait(self, seconds: float) -> 'Target':
        """等待指定的时间。
        
        Args:
            seconds: 等待的秒数
        
        Returns:
            Target: 返回自身实例，支持链式调用
        """
        Target.锁定键盘鼠标()
        time.sleep(seconds)
        Target.解锁键盘鼠标()
        return self
    
    def Send(self, text: str) -> 'Target':
        """发送文本字符串。
        
        Args:
            text: 要发送的文本
        
        Returns:
            Target: 返回自身实例，支持链式调用
        """
        pyautogui.typewrite(message=text)
        return self
    
    
    def IsExistOnWindow(self, window_title: str = None) -> bool:
        """检查目标点是否存在于指定窗口中。
        
        Args:
            window_title: 窗口标题，如果为None则使用当前活动窗口
        
        Returns:
            bool: 如果目标点在窗口范围内返回True，否则返回False
        """
        try:
            # 获取窗口信息
            import win32gui
            import win32con
            
            if window_title:
                hwnd = win32gui.FindWindow(None, window_title)
            else:
                hwnd = win32gui.GetForegroundWindow()
            
            if hwnd:
                # 获取窗口矩形
                left, top, right, bottom = win32gui.GetWindowRect(hwnd)
                # 检查点是否在窗口范围内
                return left <= self.x <= right and top <= self.y <= bottom
            return False
        except Exception as e:
            print(f"检查窗口点存在失败: {e}")
            return False
    
    def IsExistOnDesktop(self) -> bool:
        """检查目标点是否存在于桌面上。
        
        Returns:
            bool: 如果目标点在桌面范围内返回True，否则返回False
        """
        try:
            # 获取屏幕尺寸
            screen_width, screen_height = pyautogui.size()
            # 检查点是否在屏幕范围内
            return 0 <= self.x <= screen_width and 0 <= self.y <= screen_height
        except Exception as e:
            print(f"检查桌面点存在失败: {e}")
            return False
    
    
    @staticmethod
    def 锁定键盘鼠标() -> None:
        """锁定键盘和鼠标输入，防止用户干扰自动化操作。
        
        注意：该方法需要管理员权限运行。
        """
        try:
            ctypes.windll.user32.BlockInput(True)
        except Exception:
            # 如果没有管理员权限，忽略错误
            pass

    @staticmethod
    def 解锁键盘鼠标() -> None:
        """解锁键盘和鼠标输入。"""
        try:
            ctypes.windll.user32.BlockInput(False)
        except Exception:
            # 如果没有管理员权限，忽略错误
            pass
    
    @staticmethod
    def IsTemplateExistOnDesktop(template_image_path: str, threshold_value: float = 0.7) -> bool:
        """检查指定的模板图像是否存在于桌面上。
        
        Args:
            template_image_path: 模板图像的文件路径
            threshold_value: 匹配阈值，默认为0.7
        
        Returns:
            bool: 如果找到匹配的模板返回True，否则返回False
        """
        result = Target.FindTemplateImageOnScreen(template_image_path=template_image_path, threshold_value=threshold_value)
        return result is not None
        
    
    @staticmethod
    def FindTemplateImageOnScreen(template_image_path: str, threshold_value: float = 0.7) -> Optional['Target']:
        """在屏幕上查找指定的模板图像，并返回匹配位置的中心点。
        
        Args:
            template_image_path: 模板图像的文件路径，可以包含中文字符
            threshold_value: 匹配阈值，默认为0.7
        
        Returns:
            Target: 如果找到匹配，返回匹配位置的中心点；否则返回None
        """
        # 使用PilOpencvReadImage解决中文路径问题
        _template_object = PilOpencvReadImage(template_image_path)
        if _template_object is None:
            print("模板图像无法读取，请检查路径。")
            return None

        # 获取屏幕截图
        _screenshot = pyautogui.screenshot()
        _screenshot = cv2.cvtColor(np.array(_screenshot), cv2.COLOR_RGB2BGR)

        # 执行模板匹配
        _match_result = cv2.matchTemplate(_screenshot, _template_object, cv2.TM_CCOEFF_NORMED)
        # 设置匹配阈值
        _threshold = threshold_value
        _min_val, _max_val, _min_loc, _max_loc = cv2.minMaxLoc(_match_result)

        # 如果最大值大于阈值，则认为找到了匹配
        if _max_val >= _threshold:
            # 确定匹配位置的中心点
            _top_left = _max_loc
            _center_x = _top_left[0] + _template_object.shape[1] / 2
            _center_y = _top_left[1] + _template_object.shape[0] / 2
            return Target(_center_x, _center_y)
        else:
            print(f"未找到匹配的模板，最大匹配值: {_max_val:.4f}")
            return None







# 使用 PIL 配合 OpenCV 读取图片，解决中文路径问题
def PilOpencvReadImage(image_path: str) -> Optional[np.ndarray]:
    """使用PIL库读取图片并转换为OpenCV格式，解决中文路径问题。

    Args:
        image_path: 图片文件路径，可以包含中文字符

    Returns:
        np.ndarray: 读取的图片数据，以OpenCV格式返回；如果读取失败则返回None
    """
    try:
        import PIL.Image
        import io
        with open(image_path, 'rb') as f:
            img = PIL.Image.open(f)
            img_byte_arr = io.BytesIO()
            img.save(img_byte_arr, format='BMP')
            img_byte_arr = img_byte_arr.getvalue()
        return cv2.imdecode(np.frombuffer(img_byte_arr, np.uint8), cv2.IMREAD_COLOR)
    except Exception as e:
        print(f"读取图片失败: {e}")
        return None


# 使用 PIL 配合 OpenCV 保存图片，解决中文路径问题
def PilOpencvSaveImage(image: np.ndarray, image_path: str) -> bool:
    """使用PIL库保存OpenCV格式图片，解决中文路径问题。

    Args:
        image: OpenCV格式的图片数据
        image_path: 保存图片的文件路径，可以包含中文字符

    Returns:
        bool: 保存成功返回True，失败返回False
    """
    try:
        import PIL.Image
        import io
        # 将OpenCV格式转换为PIL格式
        if len(image.shape) == 3:
            # 对于彩色图像，需要从BGR转换为RGB
            rgb_image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
            pil_image = PIL.Image.fromarray(rgb_image)
        else:
            # 对于灰度图像
            pil_image = PIL.Image.fromarray(image)
        
        # 获取文件扩展名
        import os
        _, ext = os.path.splitext(image_path)
        ext = ext.lower()
        
        # 根据扩展名确定保存格式
        format_map = {
            '.jpg': 'JPEG',
            '.jpeg': 'JPEG',
            '.png': 'PNG',
            '.bmp': 'BMP',
            '.gif': 'GIF'
        }
        
        save_format = format_map.get(ext, 'JPEG')
        
        # 使用PIL保存图片
        pil_image.save(image_path, format=save_format)
        return True
    except Exception as e:
        print(f"保存图片失败: {e}")
        return False